/**
 * 
 */
package org.rollinitiative.d20.entity.skills;

import java.util.HashMap;

import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlException;
import org.exist.xmldb.XQueryService;
import org.rollinitiative.d20.entity.Ability;
import org.rollinitiative.d20.entity.InvalidEntityException;
import org.rollinitiative.d20.persist.PersistenceBridge;
import org.rollinitiative.d20.schema.skill.SkillRulesType;
import org.rollinitiative.d20.schema.skill.SkillType;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;

/**
 * @author bebopjmm
 * 
 */
public class SkillsContentBridge extends PersistenceBridge
{
    final static Log LOG = LogFactory.getLog(SkillsContentBridge.class);

    Collection skillsCollection;

    HashMap<String, Skill> skillMap = new HashMap<String, Skill>();

    HashMap<Skill, SkillType> skillToBeanMap = new HashMap<Skill, SkillType>();

    SkillRulesType rules;

    final String untrainedSkillsQuery;

    final String rulesQuery;


    public SkillsContentBridge(XMLConfiguration config)
    {
        super(config);
        untrainedSkillsQuery = config.getString("skillBridge.queries.allUntrained");
        if (untrainedSkillsQuery == null) {
            LOG.error("FAILED to retrieve query for (skillBridge.queuries.allUntrained");
        }
        rulesQuery = config.getString("skillBridge.queries.rules");
        if (rulesQuery == null) {
            LOG.error("FAILED to retrieve query for (skillBridge.queuries.allUntrained");
        }
    }


    /**
     * This method returns the set of skills that do not require training in order to use.
     * 
     * @return array of Skills representing the complete list of skills not requiring training
     */
    public Skill[] retrieveUntrainedSkills()
    {
        if (skillsCollection == null) {
            LOG
                    .error("SkillsContentBridge has not been associated to content, loadCollection needs to be invoked.");
            return new Skill[0];
        }
        LOG.info("Querying for untrained skills");
        if (untrainedSkillsQuery == null) {
            LOG.error("FAILED to retrieve query for (skillBridge.queuries.allUntrained");
            return new Skill[0];
        }
        Skill[] skills = null;
        try {
            ResourceSet results = query(untrainedSkillsQuery);
            if (results == null) {
                LOG.warn("Returning EMPTY array due to NO untrained skills being found");
                return new Skill[0];
            }
            skills = new Skill[(int) results.getSize()];
            Skill skill = null;
            String id = null;
            for (int i = 0; i < skills.length; i++) {
                SkillType skillBean = SkillType.Factory.parse(results.getResource(i).getContent()
                        .toString(), xmlOptions);
                id = skillBean.getId().getLocalPart();
                // check if we have handled this Skill already
                if (skillMap.containsKey(id)) {
                    LOG.debug("skillMap_ already contains skill, id =" + id);
                    skill = skillMap.get(id);
                }
                else {
                    LOG.debug("cacheing newly discovered skill, id =" + id);
                    skill = new Skill();
                    skill.setId(id);
                    skill.setKeyAbility(Ability.mapAbility(skillBean.xgetKeyAbility()));
                    skill.setName(skillBean.getName());
                    skillMap.put(id, skill);
                }
                skills[i] = skill;
            }

        } catch (XmlException xmlEx) {
            LOG.error("Failure marshalling into xmlbean", xmlEx);
            return new Skill[0];
        } catch (XMLDBException xmldbEx) {
            LOG.error("Failure processing ResourceSet", xmldbEx);
            return new Skill[0];
        }
        return skills;
    }


    public Skill retrieveSkill(String skillID) throws InvalidEntityException
    {
        if (skillsCollection == null) {
            LOG
                    .error("SkillsContentBridge has not been associated to content, loadCollection needs to be invoked.");
            return null;
        }

        // check if we have handled this Skill already
        if (skillMap.containsKey(skillID)) {
            return skillMap.get(skillID);
        }

        // otherwise we need to query for it.
        try {
            LOG.debug("Querying for requested skill: " + skillID);
            String skillQuery = config.getString("skillBridge.queries.skillByID");
            skillQuery = skillQuery.replace("#ID#", skillID);
            LOG.debug("Parameterized Query: " + skillQuery);
            ResourceSet results = query(skillQuery);
            if (results.getSize() < 1) {
                throw new InvalidEntityException("Specified Skill not Found: " + skillID);
            }
            SkillType skillBean = SkillType.Factory.parse(results.getResource(0).getContent()
                    .toString(), xmlOptions);
            Skill skill = new Skill();
            String id = skillBean.getId().getLocalPart();
            skill.setId(id);
            skill.setKeyAbility(Ability.mapAbility(skillBean.xgetKeyAbility()));
            skill.setName(skillBean.getName());
            skillMap.put(id, skill);
            return skill;
        } catch (XmlException e) {
            LOG.error("Failure to parse requested Class: " + skillID, e);
            throw new InvalidEntityException("Failure to retrieve specified Class: " + skillID);
        } catch (XMLDBException e) {
            LOG.error("Failure to parse requested Class: " + skillID, e);
            throw new InvalidEntityException("Failure to retrieve specified Class: " + skillID);
        }

    }


    public void loadCollection(String campaign)
    {
        try {
            connect(campaign);
            skillsCollection = retrieveCollection("abilities");
            xqueryService = (XQueryService) skillsCollection.getService("XQueryService", "1.0");
            loadRules();
        } catch (XMLDBException ex) {
            LOG.error("Failed to load the classes collection");
        }
    }


    public int getClassSkillBonus()
    {
        if (rules == null) {
            LOG
                    .error("SkillsContentBridge has not been associated to content, loadCollection needs to be invoked.");
            return 0;
        }
        return rules.getClassSkillBonus().intValue();
    }


    public int getClassSkillRankCost()
    {
        if (rules == null) {
            LOG
                    .error("SkillsContentBridge has not been associated to content, loadCollection needs to be invoked.");
            return 1;
        }
        return rules.getClassSkillRankCost().intValue();
    }


    public int getNonClassSkillRankCost()
    {
        if (rules == null) {
            LOG
                    .error("SkillsContentBridge has not been associated to content, loadCollection needs to be invoked.");
            return 2;
        }
        return rules.getNonClassSkillRankCost().intValue();
    }


    void loadRules()
    {
        if (rulesQuery == null) {
            LOG.error("FAILED to retrieve query for (skillBridge.queuries.rules");
            // establish rules defaults
            return;
        }
        // Retrieve the SkillRules and unmarshall into an xmlbean
        ResourceSet results = query(rulesQuery);
        try {
            rules = SkillRulesType.Factory.parse(results.getResource(0).getContent().toString(),
                    xmlOptions);
        } catch (XmlException e) {
            LOG.error("Failure to parse skill rules ", e);
        } catch (XMLDBException e) {
            LOG.error("Failure to parse skill rules ", e);
        }
    }
}
